package org.dbp.core.service.core.utils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.StringUtils;
import org.dbp.ReflectionDbpUtils;
import org.dbp.core.service.core.annotation.CampoDbpFiltro;

/**
 * Liberia necesaria para las utilidades
 * 
 * @author dbp
 * 
 */
public class SqlUtils {

	
	public static String plantillaInsert     = " insert into %s (%s) values(%s)";
	public static String plantillaFindId     = " select * from %s where %s = ? ";
	public static String plantillaFindAll    = " select * from %s";
	public static String plantillaFindFilter = " select * from %s where %s";
	public static String plantillaDelete     = " delete from %s where %s = ?";
	public static String plantillaUpdate     = " update %s set %s where %s = ?";
	public static String plantillaSet        =" %s = %s ";
	
	public enum TipoFiltro{
		ENTERO(" %s = ? ",Long.class),
		CADENA(" %s like '%?%' ",String.class)
		;
		private String plantilla;
		private Class<?> clase;
		private TipoFiltro(String plantilla,Class<?> clase) {
			this.plantilla = plantilla;
			this.clase=clase;
		}
		public static TipoFiltro obtenerTipo(Class<?> clase){
			TipoFiltro valdev=null;
			for(TipoFiltro tipo:TipoFiltro.values()){
				if(tipo.clase.getCanonicalName().equals(clase.getCanonicalName())){
					return tipo;
				}
			}
			return valdev;
		}
	}
	
	public static <FL extends Serializable> String generarConsultaFiltro(String tabla,FL filtro) throws Exception{
		String where=obtenerClausulaWhereFiltro(filtro);
		if(StringUtils.isBlank(where)){
			return generarFindAll(tabla);
		}else{
			return String.format(plantillaFindFilter
				, tabla
				,where);
		}
	}
	
	public static<FL extends Serializable> void cargarPreparedStamentFiltro(PreparedStatement ps,FL filtro) throws SQLException, IllegalAccessException, InvocationTargetException, NoSuchMethodException{
		if(filtro!=null){
			@SuppressWarnings("unchecked")
			Class<FL> clasesFl=(Class<FL>)filtro.getClass();
			List<Field> campos=obtenerLosCamposFiltro(clasesFl,filtro);
			int i=1;
			for(Field campo:campos){
				@SuppressWarnings("unused")
				CampoDbpFiltro campoDbpFiltro=campo.getAnnotation(CampoDbpFiltro.class);
				
				Object valor=PropertyUtils.getProperty(filtro, campo.getName());
				if(valor!=null){
				String canonical=campo.getType().getCanonicalName();
					if(StringUtils.equals(canonical,Long.class.getCanonicalName())){
						ps.setLong(i,(Long)valor);
					}else if(StringUtils.equals(canonical,String.class.getCanonicalName())){
						ps.setString(i,(String)valor);
					}
					i++;
				}
			}
		}
	}
	
	/**
	 * 
	 * Se encarga de crear la clausula where apartir de los datos del objeto filtro.
	 * 
	 * @param filtro	Es el filtro con el que vamos a trabajar
	 * @return
	 * @throws Exception 
	 */
	public static <FL extends Serializable> String obtenerClausulaWhereFiltro(FL filtro) throws Exception{
		String valdev="";
		if(filtro!=null){
			@SuppressWarnings("unchecked")
			Class<FL> clasesFl=(Class<FL>)filtro.getClass();
			List<Field> campos=obtenerLosCamposFiltro(clasesFl,filtro);
			for(Field campo:campos){
				CampoDbpFiltro campoDbpFiltro=campo.getAnnotation(CampoDbpFiltro.class);
				if(StringUtils.isNotBlank(valdev)){
					valdev+=" and ";
				}
				TipoFiltro tipo = TipoFiltro.obtenerTipo(campo.getType());
				if(tipo!=null){
					valdev+=String.format(tipo.plantilla, campoDbpFiltro.campoBd());
				}else{
					procesarError("No se corresponde el tipo en el filtro ("+campo.getType().getCanonicalName()+")");
				}
			}
		}
		return valdev;
	}
	/**
	 * Se encarga de cargar todos los campos que vamos a utilizar para filtrar. Y los 
	 * ordena por la posici�n que se establece.
	 * @param clase
	 * @return
	 * @throws NoSuchMethodException 
	 * @throws InvocationTargetException 
	 * @throws IllegalAccessException 
	 */
	public static <FL extends Serializable> List<Field> obtenerLosCamposFiltro(Class<FL> clase,FL filtro) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
		List<Field> campos = ReflectionDbpUtils.getInstancia().obtenemosLosCamposDeUnaClase(clase);
		List<Field> valdev= new ArrayList<Field>();
		Map<Integer,Field> ordenarCampo = new TreeMap<Integer,Field>(); 
		for(Field campo:campos){
			CampoDbpFiltro campoDbpFiltro=campo.getAnnotation(CampoDbpFiltro.class);
			if(campoDbpFiltro!=null 
					&& PropertyUtils.getProperty(filtro, campo.getName())!=null){
				ordenarCampo.put(campoDbpFiltro.posicion(), campo);
			}
		}
		if(ordenarCampo.size()>0){
			for(Field campo:ordenarCampo.values()){
				valdev.add(campo);
			}
		}
		return valdev;
	}
	
	private static void procesarError(String error) throws Exception{
		throw new Exception(error);
	}
	
	/**
	 * 
	 * Se encarga de generar los elementos de un update.
	 * 
	 * Nota: Este m�todo se utilizara para utilizar un update.
	 * 
	 * @param campos			Los campos con los que vamos a crear
	 * @param interrogantes		El caracterer que usaremos para sustiurilos en un preparedStament
	 * @return	
	 */
	private static String generarSetUpdate(String[] campos,String interrogantes){
		String valdev=null;
		if(campos!=null && campos.length>0){
			List<String> lista = new ArrayList<String>();
			for(String elem:campos){
				lista.add(String.format(plantillaSet, elem,interrogantes));
			}
			valdev=StringUtils.join(lista.toArray(new String[0]), ",");
		}
		return valdev;
	}
	
	public static String generarInterrogantes(String[] elementos,
			String interrogante) {
		if (elementos != null && elementos.length > 0) {
			List<String> valdev = new ArrayList<String>();
			for (String elem : elementos) {
				valdev.add(interrogante);
			}
			return StringUtils.join(valdev.toArray(new String[0]), ",");
		} else {
			return null;
		}
	}
	/**
	 * 
	 * Nos genera una consulta para obtener un elemento por ID.
	 *  
	 * @param tabla		El nombre de la tabla.
	 * @param campoId	El campo qeu hace de Id.
	 * @return
	 */
	public static String generarFindId(String tabla,String campoId){
		return String.format(plantillaFindId,tabla,campoId);
	}
	/**
	 * 
	 * Nos genera una instruccion de borrado en Sql.
	 * 
	 * @param tabla		El nombre de la tabla.
	 * @param campoId	El campo que hace de ID.
	 * @return
	 */
	public static String generarDelete(String tabla,String campoId){
		return String.format(plantillaDelete, tabla,campoId);
	}
	
	
	/**
	 * 
	 * Nos genera una consulta para recuperar
	 * 
	 * @param tabla
	 * @return
	 */
	public static String generarFindAll(String tabla){
		return String.format(plantillaFindAll, tabla);
	}
	
	public static String generarUpdate(String tabla,String campoId,String[] elementos){
		return String.format(plantillaUpdate
				, tabla
				,generarSetUpdate(elementos,"?")
				,campoId
				);
	}
	/**
	 * 
	 * Nos genera la plantilla para el inserts
	 * 
	 * @param elementos	Son lo campos que vamos a insertar
	 * @param tabla		Es el nombre de la tabla
	 * @return
	 */
	public static String generateInsert(String[] elementos,String tabla){
		return String.format(plantillaInsert
				,tabla
				,StringUtils.join(elementos,",")
				,generarInterrogantes(elementos, "?")
				);
	}
}
